package mi.payment.utils.transaction;

import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lainey.ui.utils.LogUtil;
import mi.payment.CardType;
import mi.payment.Constant;
import mi.payment.MyApplication;
import mi.payment.bean.db.TradeData;
import mi.payment.db.PayDetailModel;
import mi.payment.db.SettlementModel;
import mi.payment.db.dao.PayDetail;
import mi.payment.settings.MerManagerSettings;
import mi.payment.settings.MerchantSettingsOperation;
import mi.payment.settings.SystemParamsOperation;
import mi.payment.settings.SystemParamsSettings;
import mi.payment.ui.base.ParentActivity;
import mi.payment.utils.ByteUtil;
import mi.payment.utils.NetUtils;
import mi.payment.utils.iso8583.DecodePackage;
import mi.payment.utils.iso8583.FieldUtils;
import mi.payment.utils.iso8583.MsgField;

/**
 * 交易Task抽象类
 */
public abstract class TransactionTask<Params, Progress, Result> extends BaseCountDownTask<Params, Progress, Result> implements TransactionResultState {

    public final static String TAG = "TransactionTask";

    public Result result;
    public Params[] params;
    public String resultStr;

    public PayDetail dbBean;
    public TradeData tradeData;

    private boolean priorCorrection;                    // 是否需要事前冲正

    private boolean dbStorage = false;                  // 交易记录是否需要持久化
    private boolean updateDB = false;                   // 是否需要更新数据库的交易记录

    private boolean cupDebit = false;                   // 是否借记交易(进账为借记 支出为贷记)
    private boolean updateSettlementTable = true;       // 是否需要更新交易汇总表

    private int processCode = -1;                       // 二次处理请求码
    private boolean isNeedAuthorization = false;        // 是否需要二次授权

    protected Context context;

    private ReceiveMsgCheck mReceiveMsgCheck;
    private boolean isScriptResultUpload = false;
    private HashMap<String, MsgField> responseMap;

    protected PayDetailModel mPayDetailModel;
    protected MerManagerSettings merManagerSettings;
    protected SystemParamsSettings mSystemParamsSettings;
    protected POSPAccess pospAccess;
    protected SettlementModel mSettleModel;
    protected SystemParamsOperation mSystemParamsOperation;
    protected MerchantSettingsOperation merchantSettingsOperation;

    public TransactionTask(Context context, boolean priorCorrection) {
        this.priorCorrection = priorCorrection;
        this.context = context.getApplicationContext();
        mSettleModel = SettlementModel.getInstance();
        mPayDetailModel = PayDetailModel.getInstance();
        mSystemParamsOperation = SystemParamsOperation.getInstance();
        mSystemParamsSettings = mSystemParamsOperation.getSystemParamsSettings();
        merchantSettingsOperation = MerchantSettingsOperation.getInstance();
        merManagerSettings = merchantSettingsOperation.getMerManagerSettings();
        pospAccess = new POSPAccess();
    }

    /**
     * 是否需要授权处理
     */
    public void setNeedAuthorization(boolean isNeedAuthorization) {
        this.isNeedAuthorization = isNeedAuthorization;
    }

    /**
     * 设置交易记录是否需要持久化
     */
    public void setDBStorage(boolean isStorage) {
        this.dbStorage = isStorage;
    }

    /**
     * 是否需要更新数据库的交易记录
     */
    public void setUpdateDB(boolean updateDB) {
        this.updateDB = updateDB;
    }

    /**
     * 是否更新汇总表 (默认true, SettlementTable && DBStorage则更新汇总表)
     */
    public void setUpdateSettlementTable(boolean updateSettlementTable) {
        this.updateSettlementTable = updateSettlementTable;
    }

    /**
     * 是否借记交易 默认为贷记 (进账为借记 支出为贷记)
     */
    public void setCUPDebit(boolean cupDebit) {
        this.cupDebit = cupDebit;
    }

    /**
     * 设置处理请求的字节
     */
    public void setProcessCode(byte[] recBytes) {
        byte[] temp = new byte[1];
        System.arraycopy(recBytes, 9, temp, 0, 1);
        processCode = temp[0] & 0X0F;
    }

    private Result doTransaction(Params[] params) {

        // 初始化
        onBegin(params);

        boolean usable = NetUtils.isNetworkUsable(MyApplication.context);
        if (usable) {
            LogUtil.e(TAG, "-----------当前网络正常-------------");
        } else {
            resultStr = Constant.ANSWER_CODE_NETWORK;
            if (dbBean != null) {
                dbBean.setTradeAnswerCode(resultStr);
            }
            isTaskSucceed = true;
            return appointResult();
        }

        if (this instanceof CorrectTask) {
            LogUtil.e(TAG, "-----------该笔交易是冲正-------------");
        } else {
            // 如果脚本上送了，说明上一流水号已经使用了，需要使用新的流水号码
            if (isScriptResultUpload && dbBean != null) {
                dbBean.voucherNo = SystemParamsOperation.getInstance().getIncrementSerialNum();
            }
        }

        // 预组装
        Map<String, MsgField> requestMap = preAssembly();

        if (requestMap != null) {

            // 更新Map并组装报文(组包)，银联文档标准格式的报文
            byte[] sendBytes = changeAndAssembly(requestMap);

            if (dbBean != null) {
                dbBean.setSendBag(sendBytes);
            }

            if (dbStorage) {
                // 插入数据库
                mPayDetailModel.insert(dbBean);
            }

            // 根据不同的收单通道重组发送的报文数据
            byte[] preSendPack = regroupPackPreSend(sendBytes);

            // 报文上送
            String sendBag = "send to server : " + ByteUtil.bytes2HexStr(preSendPack);
            LogUtil.e(TAG, sendBag);

            int[] rLen = new int[1];
            byte[] recBuff = new byte[2048];
            try {
                pospAccess.Transaction(preSendPack, preSendPack.length, recBuff, rLen);
                int len = rLen[0];
                recBuff = Arrays.copyOf(recBuff, len);

                // 拆解不同收单通道的报文，重组为银联标准格式的报文数据
                recBuff = regroupPackOnResponse(recBuff);

                rLen[0] = recBuff.length;

                if (dbBean != null) {
                    dbBean.setReceiverBag(recBuff);
                }

                // 打印返回报文
                String bag = "";
                for (int i = 0; i < rLen[0]; i++) {
                    byte b = recBuff[i];
                    bag += String.format("%02X", b);
                }

                String receivedBag = "received from server : " + bag;
                LogUtil.e(TAG, receivedBag);

                // 解包
                responseMap = DecodePackage.unAssembly(recBuff);

                LogUtil.e(TAG, "  当前Task类在 " + getClass().getName() + "  被调用 ");

                // 返回报文校验
                String checkRet = checkTransactionMsg(sendBytes, requestMap, recBuff, responseMap);
                resultStr = checkRet;

                // MAC验证失败
                boolean judge = "B6".equals(checkRet) || "96".equals(checkRet);
                if (judge) {
                    if (dbBean != null) {
                        dbBean.setTradeAnswerCode(checkRet);
                    }
                    if (dbStorage || updateDB) {
                        mPayDetailModel.update(dbBean);
                    }
                    isTaskSucceed = true;
                    return appointResult();
                }

                boolean equals = "00".equals(checkRet);
                if (equals) {
                    LogUtil.e(TAG, "-----------交易成功-----------");
                } else {
                    if (dbBean != null) {
                        dbBean.setIsNeedReversal(false);
                        dbBean.setTradeAnswerCode(checkRet);
                    }
                    if (dbStorage || updateDB) {
                        mPayDetailModel.update(dbBean);
                    }
                    isTaskSucceed = true;
                    return appointResult();
                }

                MsgField f039 = responseMap.get("F039");
                if (f039 != null && f039.getDataStr() != null && f039.getDataStr().length() > 0) {
                    resultStr = f039.getDataStr();
                }

                setProcessCode(recBuff);

                // 解包后回调
                onUnpacked(responseMap);

                if (tradeData != null) { // 将返回报文回填到bean并更新数据库
                    FieldUtils.fillBeanByMap(tradeData, responseMap);
                }

                if (dbBean != null) {
                    dbBean.isNeedReversal = false;
                }

                if (dbStorage || updateDB) {
                    mPayDetailModel.update(dbBean);
                }

//                if (TextUtils.equals("00", resultStr) && updateSettlementTable && DBStorage) {
//                    PaySettlement paySettlement = mSettleModel.getPaySettlementByBatchNo(dbBean.BathNo);
//                    if (CUPDebit) {
//                        paySettlement.CUPDebitTotal += dbBean.amount;
//                        paySettlement.CUPDebitCount++;
//                    } else {
//                        paySettlement.CUPCreditTotal += dbBean.amount;
//                        paySettlement.CUPCreditCount++;
//                    }
//                    paySettlement.Operator = mi.payment.constants.Params.userInfo.getUId();
//                    paySettlement.TransBatchNo = dbBean.getBathNo();
//                    paySettlement.LocalDate = SystemDateTime.getDateByStamp(dbBean.TerminalDate);
//                    paySettlement.LocalTime = SystemDateTime.getTimeByStamp(dbBean.TerminalDate);
//                    // 更新汇总表
//                    mSettleModel.update(paySettlement);
//                }
                isTaskSucceed = true;
                return appointResult();

            } catch (Exception e) {
                e.printStackTrace();
                resultStr = e.getMessage();
                if (dbBean != null) {
                    dbBean.setTradeAnswerCode(resultStr);
                }
                if (dbStorage || updateDB) {
                    mPayDetailModel.update(dbBean);
                }
                isTaskSucceed = false;
                return appointResult();
            }
        } else {
            // 执行自己的交易逻辑，不适用基类的逻辑
            return doTransactionSelf();
        }
    }

    @Override
    protected final Result doInBackgroundImpl(Params... params) {
        this.params = params;

        // 事前冲正
        doCorrectTask();

        result = doTransaction(params);

        // 释放全局参数
        mi.payment.constants.Params.resetTradeData();

        return result;
    }

    /**
     * 用于初始化一些变量
     */
    protected abstract void onBegin(Params[] params);

    /**
     * 指定Task执行完成后的返回值
     */
    protected Result appointResult() {
        // 脚本处理
        handleScript();
        return result;
    }

    /**
     * 预组装报文域的Map集合,由子类实现
     *
     * @return 预组装好的Map集合
     */
    protected abstract Map<String, MsgField> preAssembly();

    /**
     * 更新Map并组装报文(组包)
     *
     * @return 组包后的报文
     */
    protected abstract byte[] changeAndAssembly(Map<String, MsgField> fieldMap);

    /**
     * 解包后的回调，子类可覆盖实现
     */
    protected void onUnpacked(HashMap<String, MsgField> resp) {

    }

    /**
     * 执行自己的交易逻辑，不适用基类的逻辑
     */
    protected Result doTransactionSelf() {
        return null;
    }

    /**
     * 同步执行任务（耗时操作）
     */
    public final Result syncExecute(Params... params) {
        result = doInBackgroundImpl(params);
        return result;
    }

    public final void setParams(Params[] params) {
        this.params = params;
    }

    public final Params[] getParams() {
        return params;
    }

    /**
     * 根据不同的收单通道重组发送的报文数据,如果收单通道的报文格式是银联标准格式，则不需要实现此函数
     */
    public byte[] regroupPackPreSend(byte[] sendBytes) {
        return sendBytes;
    }

    /**
     * 拆解不同收单通道的报文，重组为银联标准格式的报文数据，如果收单通道的报文格式是银联标准格式，则不需要实现此函数
     */
    public byte[] regroupPackOnResponse(byte[] receiveBytes) {
        return receiveBytes;
    }

    public void sendBroadcastForDialog(int resId) {
        String text = context.getString(resId);
        sendBroadcastForDialog(text);
    }

    public void sendBroadcastForDialog(String msg) {
        Intent intent = new Intent(ParentActivity.ACTION_CHANGE_DIALOG_TEXT);
        intent.putExtra(ParentActivity.ACTION_TEXT_EXTRA, msg);
        LocalBroadcastManager.getInstance(MyApplication.context).sendBroadcast(intent);
    }

    public void setReceiveMsgCheck(ReceiveMsgCheck transactionMsgChecker) {
        this.mReceiveMsgCheck = transactionMsgChecker;
    }


    public int getProcessCode() {
        return processCode;
    }

    @Override
    public int getTransactionResult() {
        return dbBean.getQRCodeTransState();
    }

    public String checkTransactionMsg(byte[] requestBytes, Map<String, MsgField> requestMap,
                                      byte[] responseBytes, Map<String, MsgField> responseMap) {
        if (mReceiveMsgCheck == null) {
            mReceiveMsgCheck = new TransactionMsgChecker();
        }
        return mReceiveMsgCheck.check(requestBytes, requestMap, responseBytes, responseMap);
    }

    /**
     * 执行事前冲正
     */
    private void doCorrectTask() {
        if (priorCorrection) {
            // 上送电子签名
            uploadESignData();

            // 二次授权上送
            scriptResultUpload();

            CorrectTask correctTask = new CorrectTask(context);
            correctTask.setTimeoutTask(
                    () -> LogUtil.e(TAG, "冲正超时,默认冲正成功")
            );
            PayDetail[] taskParams = correctTask.getParams();
            correctTask.syncExecute(taskParams);
        }
    }

    /**
     * 批上送电子签名
     */
    private void uploadESignData() {
        List<PayDetail> payDetailList = mPayDetailModel.getUnUploadESignList();
        if (payDetailList == null || payDetailList.size() == 0) return;
        for (PayDetail payDetail : payDetailList) {
            if (payDetail.eSignHexData == null || payDetail.eSignHexData.trim().length() < 16)
                continue;
            ESignUploadTask task = new ESignUploadTask(context);
            task.setTimeoutTask(
                    () -> LogUtil.e(Constant.TAG, "电子签名上送超时")
            );
            TradeData tradeData = mi.payment.constants.Params.newTradeData();
            tradeData.setPayDetail(payDetail);
            task.syncExecute(tradeData);
        }
    }

    private void handleScript() {
        boolean judge = isNeedAuthorization && CardType.IC.getValue() == dbBean.cardType;
        LogUtil.e(Constant.TAG, "isNeedAuthorization: " + isNeedAuthorization);
        if (judge) {
            AuthorizationProcessTools.authorizationProcess(responseMap, dbBean);
            mPayDetailModel.update(dbBean);
        } else {
            boolean equals = TextUtils.equals("00", dbBean.tradeAnswerCode);
            LogUtil.e(Constant.TAG, "tradeAnswerCode: " + dbBean.tradeAnswerCode);
            if (equals) {
                importOnlineProcessStatus(0);
            } else {
                importOnlineProcessStatus(1);
            }
        }
    }

    /**
     * 二次授权处理结果上送
     */
    private void scriptResultUpload() {
        PayDetail authPay = PayDetailModel.getInstance().getLastAurhorizationProcess();
        if (authPay != null) {
            TradeData tradeData = PayDetailModel.getInstance().getTradeDataByVoucherNo(authPay.voucherNo);
            PayDetail payDetail = tradeData.getPayDetail();
            String currentSerialNum = SystemParamsOperation.getInstance().getCurrentSerialNum();
            LogUtil.e(Constant.TAG, "currentSerialNum: " + currentSerialNum);
            payDetail.setVoucherNo(currentSerialNum);

            ICScriptResultNotificationTask task = new ICScriptResultNotificationTask(context);
            TradeData recTradeData = task.syncExecute(tradeData);
            isScriptResultUpload = true;
            boolean equals = TextUtils.equals(recTradeData.getPayDetail().getTradeAnswerCode(), "00");
            if (equals) {
                authPay.setScriptUploadStatus(2);
            } else {
                authPay.setScriptUploadStatus(3);
            }
            PayDetailModel.getInstance().update(authPay);
        }
    }

    private void importOnlineProcessStatus(int status) {
        LogUtil.e(Constant.TAG, "importOnlineProcessStatus status:" + status);
        try {
            byte[] outData = new byte[24];
            int processStatus = MyApplication.getInstance().mEMVOptV2.importOnlineProcStatus(status, null, null, outData);
            LogUtil.e(Constant.TAG, "processStatus: " + processStatus);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
